package com.redistool;

import com.fasterxml.jackson.core.type.TypeReference;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.function.Supplier;

/**
 * 基于Redis实现的锁
 */
@Slf4j
@Service
public class RedisLock {
    /**
     * 最大尝试次数
     */
    final Integer maxCount = 10;
    /**
     * 锁间隔时间 毫秒
     *
     */
    final Long interval = 1000L;

    /**
     * 默认锁超时时间 2分钟
     */
    final public static Long LOCK_TIMEOUT_DEFAULT = 3 * 60 * 1000L;

    @Resource
    private RedisUtils redisUtils;

    /**
     * 获得锁
     *
     * @param key 键值
     * @param timeout 超时时间
     * @param block   是否阻塞， 尝试十次，直到获取到锁为止 （cas 方式）
     * @return
     * @throws Exception
     */
    @SneakyThrows
    public boolean acquire(String key, Long timeout, boolean block) {
        int count = 1;

        while (true) {
            boolean flag = redisUtils.setNX(key, timeout);
            if (!flag) {
                if (block && count < maxCount) {
                    count++;
                    Thread.sleep(interval);
                    continue;
                }
            }
            return flag;
        }
    }

    /**
     * 获得锁
     *
     * @param key 键值
     * @param timeout 超时时间
     * @param block 是否阻塞
     * @param runnable 执行方法
     * @return
     * @throws Exception
     */
    @SneakyThrows
    public boolean acquire(String key, Long timeout, boolean block, Runnable runnable) {
        boolean flag = acquire(key, timeout, block);
        if (flag) {
            try {
                runnable.run();
            } catch (Exception e) {
                throw e;
            } finally {
                release(key);
            }
        }
        return flag;
    }


    /**
     * 获得锁并且失败时才释放
     *
     * @param key 键
     * @param timeout 超时时间
     * @param block 是否阻塞
     * @return
     * @throws Exception
     */
    @SneakyThrows
    public boolean acquireAndFailRelease(String key, Long timeout, boolean block, Runnable runnable) {
        boolean flag = acquire(key, timeout, block);
        if (flag) {
            try {
                runnable.run();
            } catch (Exception e) {
                release(key);
                throw e;
            }
        }
        return flag;
    }

    /**
     * 获得锁并且失败不释放
     *
     * @param key
     * @param successTimeout 成功后的锁定时长
     * @param failTimeout    失败后的锁定时长
     * @param block
     * @return
     * @throws Exception
     */
    public boolean acquireAndNotRelease(String key, Long successTimeout, Long failTimeout, boolean block, Runnable runnable) {
        boolean flag = acquire(key, successTimeout, block);
        if (flag) {
            try {
                runnable.run();
            } catch (Exception e) {
                if (failTimeout != null) {
                    redisUtils.expire(key, failTimeout);
                }
                throw e;
            }
        }
        return flag;
    }

    /***
     * 释放锁
     *
     * @param key
     */
    @SneakyThrows
    public void release(String key) {
        int count = 1;

        while (true) {
            try {
                redisUtils.delete(key);
                break;
            } catch (Exception e) {
                log.error("redislock release key failure:", e);
                if (count < maxCount) {
                    count++;
                    Thread.sleep(interval);
                } else {
                    break;
                }
            }
        }
    }

    /**
     * 尝试读取缓存，结果为null的时候先加锁后执行supplier，
     * 获取不到锁的时候循环等待，然后读取缓存返回
     */
    @SneakyThrows
    public <T> T getIfNullLock(String key, TypeReference<T> valueTypeRef, Long timeout, Supplier<T> supplier) {
        T value = redisUtils.get(key, valueTypeRef);
        boolean flag = false;
        String lockRedisKey = "RedisLock:getIfNullLock:" + key;
        if (value == null) {
            flag = acquire(lockRedisKey, 5 * 60 * 1000L, true);
            value = redisUtils.get(key, valueTypeRef);
        }
        if (flag && value == null) {
            //加锁成功，执行supplier，并存储
            value = supplier.get();
            redisUtils.set(key, value, timeout);
        }
        release(lockRedisKey);
        return value;
    }

    @SneakyThrows
    public <T> T getIfNullLock(String key, Class<T> clz, Long timeout, Supplier<T> supplier) {
        T value = redisUtils.get(key, clz);
        boolean flag = false;
        String lockRedisKey = "RedisLock:getIfNullLock:" + key;
        if (value == null) {
            flag = acquire(lockRedisKey, 5 * 60 * 1000L, true);
            value = redisUtils.get(key, clz);
        }
        if (flag && value == null) {
            //加锁成功，执行supplier，并存储
            value = supplier.get();
            redisUtils.set(key, value, timeout);
        }
        release(lockRedisKey);
        return value;
    }

    @SneakyThrows
    public <T> void lockAndSet(String key, Long timeout, Supplier<T> supplier) {
        String lockRedisKey = "RedisLock:lockAndSet:" + key;
        boolean flag = acquire(lockRedisKey, 5 * 60 * 1000L, false);
        if (flag) {
            T value = supplier.get();
            redisUtils.set(key, value, timeout);
        }
        release(lockRedisKey);
    }

    /**
     * 锁续期
     */
    public void lockRenewal(String key, Long timeout) {
        redisUtils.expire(key, timeout);
    }

}
